package utils
{
	import com.adobe.utils.AGALMiniAssembler;
	
	import flash.display3D.Context3D;
	import flash.display3D.Context3DBlendFactor;
	import flash.display3D.Context3DCompareMode;
	import flash.display3D.Context3DProgramType;
	import flash.display3D.Context3DTriangleFace;
	import flash.display3D.Context3DVertexBufferFormat;
	import flash.display3D.Program3D;
	import flash.display3D.textures.Texture;
	import flash.geom.Matrix3D;
	
	public class Molehill_particle extends Molehill_entity
	{
		
		public var active:Boolean = true;
		public var age:uint = 0;
		public var age_max:uint = 1000;
		public var step_counter:uint = 0;
		private var mesh2:Molehill_obj_parser;
		private var age_scale:Vector.<Number> = new Vector.<Number>([1, 0, 1, 1]);
		private var rgba_scale:Vector.<Number> = new Vector.<Number>([1, 1, 1, 1]);
		private var start_size:Number = 0;
		private var end_size:Number = 1;
		
		override public function Molehill_particle(mydata:Class = null,mycontext:Context3D = null,
												   mytexture:Texture = null,mydata2:Class = null)
		{
			transform = new Matrix3D();
			context = mycontext;
			texture = mytexture;
			// use a shader specifically designed for particles
			// this version is for two frames: interpolating from one
			// mesh to another over time
			if (context && mydata2) 
				initParticleShader(true);
				// only one mesh defined: use the simpler shader
			else if (context)
				initParticleShader(false);
			if (mydata && context)
			{
				mesh = new Molehill_obj_parser(mydata, context, 1, true, true);
				polycount = mesh.indexBufferCount;
				trace("Mesh has " + polycount + " polygons.");
			}
			// parse the second mesh
			if (mydata2 && context)
				mesh2 = new Molehill_obj_parser(mydata2, context, 1, true, true);
			// default a render state suitable for particles
			blend_src = Context3DBlendFactor.ONE;
			blend_dst = Context3DBlendFactor.ONE;
			culling_mode = Context3DTriangleFace.NONE;
			depth_test_mode = Context3DCompareMode.ALWAYS;
			depth_test = false;
		}
		
		public function cloneparticle():Molehill_particle
		{
			var myclone:Molehill_particle = new Molehill_particle();
			updateTransformFromValues();
			myclone.transform = this.transform.clone();
			myclone.mesh = this.mesh;
			myclone.texture = this.texture;
			myclone.shader = this.shader;
			myclone.vertexBuffer = this.vertexBuffer;
			myclone.indexBuffer = this.indexBuffer;
			myclone.context = this.context;
			myclone.updateValuesFromTransform();
			myclone.mesh2 = this.mesh2;
			myclone.start_size = this.start_size;
			myclone.end_size = this.end_size;
			myclone.polycount = this.polycount;
			return myclone;
		}
		
		private var two_pi:Number = 2*Math.PI;
		// returns a float from -amp to +amp in wobbles per second
		private function wobble(ms:Number = 0, amp:Number = 1, spd:Number = 1):Number
		{
			var val:Number;
			val = amp*Math.sin((ms/1000)*spd*two_pi);
			return val;
		}
		// returns a float that oscillates from 0..1..0 each second
		private function wobble010(ms:Number):Number
		{
			var retval:Number;
			retval = wobble(ms-250, 0.5, 1.0) + 0.5;
			return retval;
		}
		
		public function step(ms:uint):void
		{
			step_counter++;
			age += ms;
			if (age >= age_max)
			{
				//trace("Particle died (" + age + "ms)");
				active = false;
				return;
			}
			// based on age, change the scale for starting pos (1..0)
			age_scale[0] = 1 - (age / age_max);
			// based on age, change the scale for ending pos (0..1)
			age_scale[1] = age / age_max;
			// based on age: go from 0..1..0
			age_scale[2] = wobble010(age);
			// ensure it is within the valid range
			if (age_scale[0] < 0) age_scale[0] = 0;
			if (age_scale[0] > 1) age_scale[0] = 1;
			if (age_scale[1] < 0) age_scale[1] = 0;
			if (age_scale[1] > 1) age_scale[1] = 1;
			if (age_scale[2] < 0) age_scale[2] = 0;
			if (age_scale[2] > 1) age_scale[2] = 1;
			// fade alpha in and out
			/*rgba_scale[0] = age_scale[0];
			rgba_scale[1] = age_scale[0];
			rgba_scale[2] = age_scale[0];
			rgba_scale[3] = age_scale[2];*/
			
			//trace(int(age_scale[2]*1000))
			
			rgba_scale[0] = age_scale[0];
			rgba_scale[1] = age_scale[0];
			rgba_scale[2] = age_scale[0];
			rgba_scale[3] = 1;
		}
		
		public function respawn(pos:Matrix3D, maxage:uint = 1000,
			scale1:Number = 0, scale2:Number = 50):void
		{
			age = 0;
			step_counter = 0;
			age_max = maxage;
			transform = pos.clone();
			updateValuesFromTransform();
			rotationDegreesX = 180; // point "down"
			// start at a random orientation each time
			rotationDegreesY = Math.random() * 360 - 180;
			updateTransformFromValues();
			age_scale[0] = 1;
			age_scale[1] = 0;
			age_scale[2] = 0;
			age_scale[3] = 1;
			rgba_scale[0] = 1;
			rgba_scale[1] = 1;
			rgba_scale[2] = 1;
			rgba_scale[3] = 1;
			start_size = scale1;
			end_size = scale2;
			active = true;
			//trace("Respawned particle at " + pos_string());
		}
		
		override public function render(view:Matrix3D,projection:Matrix3D):void
		{
			// only render if these are set
			if (!active) return;
			if (!mesh) return;
			if (!mesh2) return;
			if (!context) return;
			if (!shader) return;
			if (!texture) return;
			// get bigger over time
			scaleXYZ = start_size + ((end_size - start_size) * age_scale[1]);
			//Reset our matrix
			matrix.identity();
			matrix.append(transform);
			if (_following) matrix.append(_following.transform);
			matrix.append(view);
			matrix.append(projection);
			// Set the vertex program register vc0
			// to our model view projection matrix
			context.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, matrix, true);
			
			// Set the vertex program register vc4
			// to our time scale from (0..1)
			// used to interpolate vertex position over time
			context.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 4, age_scale);
		
			// Set the fragment program register fc0
			// to our time scale from (0..1)
			// used to interpolate transparency over time
			context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, rgba_scale);
			
			// Set the AGAL program
			context.setProgram(shader);
			// Set the fragment program register ts0 to a texture
			context.setTextureAt(0,texture);
			// starting position (va0)
			context.setVertexBufferAt(0, mesh.positionsBuffer,0, Context3DVertexBufferFormat.FLOAT_3);
			// tex coords (va1)
			context.setVertexBufferAt(1, mesh.uvBuffer,0, Context3DVertexBufferFormat.FLOAT_2);
			// final position (va2)
			if (mesh2)
			{
				context.setVertexBufferAt(2, mesh2.positionsBuffer,0, Context3DVertexBufferFormat.FLOAT_3);
			}
			// set the render state
			context.setBlendFactors(blend_src, blend_dst);
			context.setDepthTest(depth_test,depth_test_mode);
			context.setCulling(culling_mode);
			// render it
			context.drawTriangles(mesh.indexBuffer,0, mesh.indexBufferCount);
			
		}
		
		private function initParticleShader(twomodels:Boolean=false):void
		{
			var vertexShader:AGALMiniAssembler = new AGALMiniAssembler();
			var fragmentShader:AGALMiniAssembler = new AGALMiniAssembler();
			if (twomodels)
			{
				trace("Compiling the TWO FRAME particle shader...");
				vertexShader.assemble
					(
						Context3DProgramType.VERTEX,
						// scale the starting position
						"mul vt0, va0, vc4.xxxx\n" +
						// scale the ending position
						"mul vt1, va2, vc4.yyyy\n" +
						// interpolate the two positions
						"add vt2, vt0, vt1\n" +
						// 4x4 matrix multiply to get camera angle
						"m44 op, vt2, vc0\n" +
						// tell fragment shader about UV
						"mov v1, va1"
					);
			}
			else
			{
				trace("Compiling the ONE FRAME particle shader...");
				vertexShader.assemble
					(
						Context3DProgramType.VERTEX,
						// get the vertex pos multiplied by camera angle
						"m44 op, va0, vc0\n" +
						// tell fragment shader about UV
						"mov v1, va1"
					);
			}
			
			// textured using UV coordinates
			fragmentShader.assemble
				(
					Context3DProgramType.FRAGMENT,
					// grab the texture color from texture 0
					// and uv coordinates from varying register 1
					// and store the interpolated value in ft0
					"tex ft0, v1, fs0 <2d,linear,repeat,miplinear>\n" +
					// multiply by "fade" color register (fc0)
					"mul ft0, ft0, fc0\n" +
					// move this value to the output color
					"mov oc, ft0\n"
				);
			
			shader = context.createProgram();
			shader.upload(
				vertexShader.agalcode,
				fragmentShader.agalcode);
			
		}
		/**end*/
	}
}